/**
 * This file is part of the Kompics P2P Framework.
 * 
 * Copyright (C) 2009 Swedish Institute of Computer Science (SICS)
 * Copyright (C) 2009 Royal Institute of Technology (KTH)
 *
 * Kompics is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package control;

import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import java.util.TreeMap;
import java.util.UUID;

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.NotFoundException;
import javassist.util.HotSwapper;

import javax.imageio.ImageIO;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

import org.apache.commons.codec.binary.Base64;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.statistics.HistogramDataset;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.jdi.connect.IllegalConnectorArgumentsException;

import common.configuration.Global;
import common.peer.PeerAddress;
import common.simulation.PeerFail;
import common.simulation.PeerJoin;
import common.simulation.SimulatorPort;

import se.sics.kompics.ComponentDefinition;
import se.sics.kompics.Handler;
import se.sics.kompics.Negative;
import se.sics.kompics.Positive;
import se.sics.kompics.address.Address;
import se.sics.kompics.network.Network;
import se.sics.kompics.p2p.overlay.OverlayAddress;
import se.sics.kompics.timer.CancelTimeout;
import se.sics.kompics.timer.ScheduleTimeout;
import se.sics.kompics.timer.Timer;
import se.sics.kompics.web.Web;
import se.sics.kompics.web.WebRequest;
import se.sics.kompics.web.WebResponse;
import tman.main.GraphPanel;
import tman.main.GraphPanel3;
import common.peer.PeerInfo;
import tman.simulator.snapshot.GraphUtil;
import tman.simulator.snapshot.StateUtil;
import tman.system.peer.tman.PeerData;
import se.sics.kompics.p2p.experiment.dsl.distribution.BigIntegerNormalDistribution;
import se.sics.kompics.p2p.experiment.dsl.distribution.BigIntegerUniformDistribution;
import se.sics.kompics.p2p.experiment.dsl.distribution.Distribution;
import se.sics.kompics.p2p.experiment.dsl.SimulationScenario;

/**
 * The <code>CyclonMonitorServer</code> class.
 * 
 * @author Cosmin Arad <cosmin@sics.se>
 * @version $Id$
 */
public class ControlServer extends ComponentDefinition {

	Positive<Network> network = positive(Network.class);
	Positive<Timer> timer = positive(Timer.class);
	Negative<Web> web = negative(Web.class);
	Negative<SimulatorPort> simulator = negative(SimulatorPort.class);

	private static final Logger logger = LoggerFactory
			.getLogger(ControlServer.class);

	public ControlServer() {
		
		subscribe(handleInit, control);

		subscribe(handleWebRequest, web);
	}

	private Handler<ControlServerInit> handleInit = new Handler<ControlServerInit>() {
		public void handle(ControlServerInit event) {

			logger.debug("INIT");

			try {
				UIManager.setLookAndFeel(UIManager
						.getCrossPlatformLookAndFeelClassName());
			} catch (ClassNotFoundException e1) {
				e1.printStackTrace();
			} catch (InstantiationException e1) {
				e1.printStackTrace();
			} catch (IllegalAccessException e1) {
				e1.printStackTrace();
			} catch (UnsupportedLookAndFeelException e1) {
				e1.printStackTrace();
			}
		}
	};


	private Handler<WebRequest> handleWebRequest = new Handler<WebRequest>() {
		public void handle(WebRequest event) {
			logger.debug("Handling WebRequest");

			String peers = event.getRequest().getParameter("peers");
			boolean join = event.getRequest().getParameter("distribution") != null && event.getRequest().getParameter("jq") != null;
			boolean fail = event.getRequest().getParameter("fq") != null;
			boolean rf = event.getRequest().getParameter("code") != null;
			
			if(join)
			{
				//Generate join event(s)
				
				BigInteger Param1 = BigInteger.valueOf(Integer.parseInt(event.getRequest().getParameter("param1")));
				Integer count = Integer.valueOf(event.getRequest().getParameter("jq"));
				//Peer ID generation doesn't follow a distribution (i.e. it's given an initial constant value)
				if(event.getRequest().getParameter("distribution").equals("uniform"))
				{
					BigInteger Param2 = BigInteger.valueOf(Integer.parseInt(event.getRequest().getParameter("param2")));
					BigIntegerUniformDistribution dist = new BigIntegerUniformDistribution(Param1, Param2, new Random(Global.seed));
					for(int i=0; i<count; i++)
					{
						trigger(new PeerJoin(new BigInteger("10000"), dist.draw().intValue()), simulator);
					}
				}
				else if(event.getRequest().getParameter("distribution").equals("normal"))
				{
					BigInteger Param2 = BigInteger.valueOf(Integer.parseInt(event.getRequest().getParameter("param2")));
					BigIntegerNormalDistribution dist = new BigIntegerNormalDistribution(Param1, Param2, new Random(Global.seed));
					for(int i=0; i<count; i++)
					{
						trigger(new PeerJoin(new BigInteger("10000"), dist.draw().intValue()), simulator);
					}
				}
				else if(event.getRequest().getParameter("distribution").equals("constant"))
				{
					for(int i=0; i<count; i++)
					{
						trigger(new PeerJoin(new BigInteger("10000"), Param1.intValue()), simulator);
					}
				}
			}
			if(fail)
			{
				//Generate fail event(s)
				BigInteger count = BigInteger.valueOf(Integer.parseInt(event.getRequest().getParameter("fq")));
				trigger(new PeerFail(count, true), simulator);
			}
			if(rf)
			{
				//Modify ranking function during runtime
				String code = event.getRequest().getParameter("code");
				ClassPool pool = ClassPool.getDefault();
				CtClass cc;
				try {
					cc = pool.get("common.peer.PeerInfo");
					CtMethod m;
					m = cc.getDeclaredMethod("favoredChild");
					m.setBody(code);
					//cc.writeFile();
					 byte[] classFile = cc.toBytecode();
					 HotSwapper hs = new HotSwapper(8000);  // 8000 is a port number.
					 hs.reload("common.peer.PeerInfo", classFile);

							 
				} catch (NotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (CannotCompileException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (IllegalConnectorArgumentsException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

			boolean showPeers = peers == null ? false
					: peers.equals("on") ? true : false;

			String html = dumpControlsToHtml(showPeers);
			WebResponse response = new WebResponse(html, event, 1, 1);
			trigger(response, web);
		}
	};

	private String dumpControlsToHtml(boolean showPeers) {
		StringBuilder sb = new StringBuilder("<!DOCTYPE html PUBLIC ");
		sb.append("\"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3");
		sb.append(".org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"><html xmlns=");
		sb.append("\"http://www.w3.org/1999/xhtml\"><head><meta http-equiv=\"");
		sb.append("Content-Type\" content=\"text/html; charset=utf-8\" />");

		sb.append("<title>Configuration Manager</title>");
		sb.append("<style type=\"text/css\"><!--.style2 {font-family: ");
		sb.append("Arial, Helvetica, sans-serif; color: #0099FF;}--></style>");
		sb.append("</head><body><h1 align=\"center\" class=\"style2\">");
		sb.append("Configuration Manager</h1>");
		
		//Peer Join
		sb.append("<h2 align=\"left\" class=\"style2\">");
		sb.append("Node Joins:</h2>");
		sb.append("<form name='join' action='html_form_action.asp' method='get'>");
		sb.append("Distribution <select name='distribution'>");
		sb.append("<option value='constant'>Constant</option>");
		sb.append("<option value='uniform'>Uniform</option>");
		sb.append("<option value='normal' selected>Normal</option>");
		sb.append("</select>");
		sb.append(" Param 1 <input type ='text' name='param1' size='2'>");
		sb.append(" Param 2 <input type ='text' name='param2' size='2'>");
		sb.append(" Quantity <input type ='number' name='jq' size='2'>");
		sb.append("</br> </br> <input type='submit' value='Generate'> </form> ");
		
		//Peer Fail
		sb.append("<h2 align=\"left\" class=\"style2\">");
		sb.append("Node Failures:</h2>");
		sb.append("<form name='fail' action='html_form_action.asp' method='get'>");
		sb.append("Quantity <input type ='number' name='fq' size='2'> out of -INSERT TOTAL NO. OF NODES-");
		sb.append("</br> </br> <input type='submit' value='Generate'> </form> ");
		
		//Ranking Function
		sb.append("<h2 align=\"left\" class=\"style2\">");
		sb.append("Ranking Function:</h2>");
		sb.append("<form name='rf' action='html_form_action.asp' method='get'>");
		sb.append("Code: </br><TEXTAREA NAME='code' ROWS=3 COLS=30 ></TEXTAREA>");
		sb.append("</br> </br> <input type='submit' value='Modify'> </form> ");
		return sb.toString();
	}

	private String durationToString(long duration) {
		StringBuilder sb = new StringBuilder();

		// get duration in seconds
		duration /= 1000;

		int s = 0, m = 0, h = 0, d = 0, y = 0;
		s = (int) (duration % 60);
		// get duration in minutes
		duration /= 60;
		if (duration > 0) {
			m = (int) (duration % 60);
			// get duration in hours
			duration /= 60;
			if (duration > 0) {
				h = (int) (duration % 24);
				// get duration in days
				duration /= 24;
				if (duration > 0) {
					d = (int) (duration % 365);
					// get duration in years
					y = (int) (duration / 365);
				}
			}
		}

		boolean printed = false;

		if (y > 0) {
			sb.append(y).append("y");
			printed = true;
		}
		if (d > 0) {
			sb.append(d).append("d");
			printed = true;
		}
		if (h > 0) {
			sb.append(h).append("h");
			printed = true;
		}
		if (m > 0) {
			sb.append(m).append("m");
			printed = true;
		}
		if (s > 0 || printed == false) {
			sb.append(s).append("s");
		}

		return sb.toString();
	}
}
